import Vue, { nextTick } from 'vue';
import { GlForm, GlAlert, GlButton } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import * as Sentry from '@sentry/browser';
import { redirectTo } from '~/lib/utils/url_utility'; // eslint-disable-line import/no-deprecated
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createVulnerabilityMutation from 'ee/security_dashboard/graphql/mutations/vulnerability_create.mutation.graphql';
import NewVulnerability from 'ee/vulnerabilities/components/new_vulnerability/new_vulnerability.vue';
import SectionName from 'ee/vulnerabilities/components/new_vulnerability/section_name.vue';
import SectionIdentifiers from 'ee/vulnerabilities/components/new_vulnerability/section_identifiers.vue';
import SectionDetails from 'ee/vulnerabilities/components/new_vulnerability/section_details.vue';
import SectionSolution from 'ee/vulnerabilities/components/new_vulnerability/section_solution.vue';

Vue.use(VueApollo);

jest.mock('@sentry/browser');

jest.mock('~/lib/utils/url_utility', () => ({
  redirectTo: jest.fn(),
}));

describe('New vulnerability component', () => {
  const projectId = '22';
  const vulnerabilityReportPath = '/root/security-reports/-/vulnerability_report';

  let wrapper;

  const inputs = {
    sectionName: {
      vulnerabilityName: 'CVE 2050',
      vulnerabilityDesc: 'Password leak',
    },
    sectionDetails: {
      severity: 'low',
      detectionMethod: 2,
      status: 'confirmed',
    },
    sectionIdentifiers: {
      identifiers: [
        {
          name: 'CWE-94',
          url: 'https://cwe.mitre.org/data/definitions/94.html',
        },
      ],
    },
    sectionSolution: {
      solution: 'This is the solution of the vulnerability.',
    },
  };

  const findForm = () => wrapper.findComponent(GlForm);
  const findAlert = () => wrapper.findComponent(GlAlert);
  const findSectionName = () => wrapper.findComponent(SectionName);
  const findSectionDetails = () => wrapper.findComponent(SectionDetails);
  const findSectionSolution = () => wrapper.findComponent(SectionSolution);
  const findSectionIdentifiers = () => wrapper.findComponent(SectionIdentifiers);
  const findSubmitButton = () => wrapper.findAllComponents(GlButton).at(0);
  const findCancelButton = () => wrapper.findAllComponents(GlButton).at(1);

  const createWrapper = ({ handler } = {}) => {
    wrapper = shallowMountExtended(NewVulnerability, {
      apolloProvider: createMockApollo([[createVulnerabilityMutation, handler]]),
      provide: {
        projectId,
        vulnerabilityReportPath,
      },
    });
  };

  describe('page structure', () => {
    beforeEach(() => {
      createWrapper();
    });

    it('should render the page title and description', () => {
      expect(wrapper.findByRole('heading', { name: 'Add vulnerability finding' }).exists()).toBe(
        true,
      );
      expect(wrapper.findByTestId('page-description').text()).toBe(
        'Manually add a vulnerability entry into the vulnerability report.',
      );
    });

    it('contains a form', () => {
      expect(wrapper.findComponent(GlForm).exists()).toBe(true);
    });

    it('has autocomplete turned off', () => {
      expect(wrapper.findComponent(GlForm).attributes('autocomplete')).toBe('off');
    });

    it('contains a submit button', () => {
      expect(findSubmitButton().exists()).toBe(true);
    });

    it('contains a cancel button', () => {
      expect(findCancelButton().attributes()).toMatchObject({
        href: vulnerabilityReportPath,
        type: 'button',
      });
    });
  });

  describe('form submission', () => {
    const updateFormValuesAndSubmitForm = async () => {
      findSectionName().vm.$emit('change', inputs.sectionName);
      findSectionIdentifiers().vm.$emit('change', inputs.sectionIdentifiers);
      findSectionDetails().vm.$emit('change', inputs.sectionDetails);
      findSectionSolution().vm.$emit('change', inputs.sectionSolution);
      findForm().vm.$emit('submit', { preventDefault: jest.fn() });
      await nextTick();
      expect(findSubmitButton().props('loading')).toBe(true);
      await waitForPromises();
    };

    it('handles form validation', async () => {
      const mutationHandlerMock = jest.fn().mockResolvedValue();
      const validationState = {
        severity: false,
        status: false,
        name: false,
        identifiers: [
          {
            identifierCode: false,
            identifierUrl: false,
          },
        ],
      };

      createWrapper({ handler: mutationHandlerMock });
      findForm().vm.$emit('submit', { preventDefault: jest.fn() });
      await waitForPromises();
      expect(mutationHandlerMock).not.toHaveBeenCalled();
      expect(findSectionDetails().props('validationState')).toEqual(validationState);
      expect(findSectionName().props('validationState')).toEqual(validationState);
      expect(findSectionIdentifiers().props('validationState')).toEqual(validationState);
    });

    it('submits the form successfully', async () => {
      const mutationHandlerMock = jest.fn().mockResolvedValue({
        data: {
          vulnerabilityCreate: {
            vulnerability: {
              id: 'gid://gitlab/Vulnerability/20345379',
              vulnerabilityPath: '/path/to/vulnerability/20345379',
            },
            errors: null,
          },
        },
      });

      createWrapper({ handler: mutationHandlerMock });
      await updateFormValuesAndSubmitForm();

      expect(mutationHandlerMock).toHaveBeenCalledWith(
        expect.objectContaining({
          input: {
            description: 'Password leak',
            identifiers: [
              {
                url: 'https://cwe.mitre.org/data/definitions/94.html',
                name: 'CWE-94',
              },
            ],
            scanner: {
              id: 'gitlab-manual-vulnerability-report',
              name: 'manually-created-vulnerability',
              url: 'https://gitlab.com',
              version: '1.0',
              vendor: {
                name: 'GitLab',
              },
            },
            name: 'CVE 2050',
            project: 'gid://gitlab/Project/22',
            severity: 'LOW',
            solution: 'This is the solution of the vulnerability.',
            state: 'CONFIRMED',
          },
        }),
      );

      expect(redirectTo).toHaveBeenCalledWith('/path/to/vulnerability/20345379'); // eslint-disable-line import/no-deprecated
      expect(findAlert().exists()).toBe(false);
    });

    it('handles form submission error and displays alert component when there are errors', async () => {
      createWrapper({
        handler: jest.fn().mockResolvedValue({
          data: {
            vulnerabilityCreate: {
              vulnerability: null,
              errors: [{ message: 'Something went wrong' }],
            },
          },
        }),
      });
      await updateFormValuesAndSubmitForm();
      expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated
      expect(Sentry.captureException).not.toHaveBeenCalled();
      await nextTick();
      expect(findAlert().exists()).toBe(true);
    });

    it('handles form submission error and logs to sentry when the error is unknown', async () => {
      createWrapper({
        handler: jest.fn().mockRejectedValue({
          data: {
            vulnerabilityCreate: {
              vulnerability: null,
            },
          },
        }),
      });

      await updateFormValuesAndSubmitForm();
      expect(redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated
      expect(Sentry.captureException).toHaveBeenCalled();
      await nextTick();
      expect(findAlert().exists()).toBe(true);
    });
  });
});
